/*************************************************************************** 

QGCIO: Class definitions for classes involved with model IO.

 *************************************************************************** */

#include "qgcmio.h"

#include <string>
#include <vector>
#include <stdlib.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <malloc.h>

#include "misclib.h"
#include "diagfuncs.h"

#ifndef QGCM_INPUT_OUTPUT_SRC_
#define QGCM_INPUT_OUTPUT_SRC_

using namespace std;
/*************************************************************************** 
  class definition qstream:
 **************************************************************************/
qstream::qstream(){
	stream = NULL;
	ftype  = closed;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
qstream::qstream(FILE * bufr){ // construct using FILE handle
	stream = bufr;
	ftype = internal; // internal types ignore close requests
};
#ifdef DEBUG
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
const qstream & qstream::getroutqstream()
{
	static qstream qrIO(getrout()); // return internal stream ptr
	return qrIO;                    // to the debug remote output terminal
}
#endif
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void qstream::set_ftype(){
	if( this->path.size() == 0 ){
		this->ftype = closed; 
	}else if( is_file_terminal_device(this->path.c_str()) ){
		this->ftype = terminal; 
	}else if( is_file_socket( this->path.c_str() ) ){
		this->ftype = socket; 
	}else if( is_file_pipe( this->path.c_str() ) ){
		this->ftype = pipe; 
	}else{
		this->ftype = regular;
	}
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
bool qstream::reopen(){
	if( this->ftype == internal ) return true; // ignore request, but return success
	if( this->path.size() > 0 ){ 
		// the open function automatically closes a stream if already opened
		return this->open(this->path.c_str(), this->ifmt.c_str());
	}
	return false;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
bool qstream::open(FILE * bufr){
	this->close();
	this->stream = bufr;
	this->ftype = internal;
	return true;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
bool qstream::open(const char * path, const char * fmt){
	this->close(); 
	this->stream = fopen(path, fmt); // use c interface
	if(!this->stream) return false;
	this->path=path;
	this->ifmt=fmt;
	this->set_ftype();
	return true;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
qstream & qstream::operator=(const qstream & oq){
	// direct assigment
	// Note that if two qstreams reference the same file handle
	// and one is closed, the other will not know about 
	// the other being closed. This is the same behaviour as in the c language.
	this->stream = oq.stream;
	this->ftype = oq.ftype;
	this->path = oq.path;
	this->ifmt = oq.ifmt;
	return *this;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
qstream::qstream(const qstream & oq){
	this->stream = oq.stream;
	this->ftype = oq.ftype;
	this->path = oq.path;
	this->ifmt = oq.ifmt;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
bool qstream::close(){
	if( this->stream && this->ftype != internal ){
		fclose(this->stream);
	}
	// if ftype is internal just discard the file handle
	this->stream = NULL;
	this->path.clear();
	this->ifmt.clear();
	this->ftype = closed; // this qstream was explicitly closed
	return true;
}; 
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
qstream::~qstream(){
	// qstream interface is basically intended to emulate both
	// the c printf, putc, etc stdio functions
	// and the insertion stream operators from c++.
	// Nothing to do here.
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
bool qstream::is_closed() const {
	return this->ftype == closed;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
FILE * qstream::get_handle() {
	return this->stream;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void qstream::flush() const {
	if( this->stream ){
		fflush(this->stream);
	}
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int qstream::scanf(const char * fmt, ...) const {
	va_list(arg);
	va_start(arg, fmt);
	if( this->stream ){
		int num = vfscanf(this->stream, fmt, arg);
		return num;
	}
	return 0;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
bool qstream::putc(char c) const{
	if( this->stream ){
		return (fputc(c, this->stream) != EOF);
	}
	return false;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int qstream::vprintf(const char * fmt, va_list arg) const{
	if( this->stream ){
		int num = vfprintf( this->stream, fmt, arg );
		return num;
	}
	return 0;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int qstream::printf(const char * fmt, ...) const {
	va_list(arg);
	va_start(arg, fmt);
	if( this->stream ){
		int num = vfprintf( this->stream, fmt, arg );
		return num;
	}
	return 0;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
bool qstream::is_pipe() const{
	return this->ftype == pipe;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
bool qstream::is_socket() const{
	return this->ftype == socket;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
bool qstream::is_terminal_device() const{
	return this->ftype == terminal;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
bool qstream::is_interactive_input() const {
	if ( this->is_closed() ) return false;
	if ( this-> stream == stdin ) return true; // special case
	return ( this->is_fifo() ||
			this->is_pipe() ||
			this->is_terminal_device() || 
			this->is_socket() 
	       );
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
const string & qstream::getpath() const{
	return this->path;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
bool qstream::is_fifo() const{
	return this->ftype == pipe;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
const qstream & qstream::operator<<(const float & fval) const {
	if( this-> stream ){
		fprintf(this->stream, "%f", fval);
	}
	return *this;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
const qstream & qstream::operator<<(const string & sval) const {
	if( this-> stream ){
		fprintf(this->stream, "%s", sval.c_str());
	}
	return *this;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
const qstream & qstream::operator<<(const IValue & val) const{
	// Allow printing of diagnostic value types
	string str = cast_to_string(val);
	this->printf("%s", str.c_str());
	return *this;
};
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
const qstream & qstream::operator<<(const int & ival) const {
	if( this-> stream ){
		fprintf(this->stream, "%i", ival);
	}
	return *this;
};
/*************************************************************************** 
  End class qstream
 **************************************************************************/


// -----------------------------------------------------------------------------
// section below is POSIX only compatible
// -----------------------------------------------------------------------------
#include <sys/stat.h>
#include <error.h>

bool is_file_terminal_device(const char * fname){
	struct stat finfo;
	if( stat(fname, &finfo) < 0 ) return false;
	if(S_ISCHR(finfo.st_mode) != 0){
		return true;
	}
	return false;
};

bool is_file_socket(const char * fname){
	struct stat finfo;
	if( stat(fname, &finfo) < 0) return false;
	if(S_ISSOCK(finfo.st_mode) != 0) return true;
	return false;
}

bool is_file_pipe(const char * fname){
	struct stat finfo;
	if( stat(fname, &finfo) < 0) return false;
	if(S_ISFIFO(finfo.st_mode) != 0) return true;
	return false;
};

bool is_file_regular(const char * fname){
	struct stat finfo;
	if( stat(fname, &finfo) < 0 ) return false;
	if(S_ISREG(finfo.st_mode)){
		return true;
	}
	return false;
};
/* ------------------------------------------------------------------------- */
int read_fifo(const char * filename, vector<string> & input_lines){
	// first create the fifo
	int mode = S_IWUSR | S_IRUSR;
	int rvalue =  mkfifo(filename, mode);
	// try to create a fifo, if it already exists
	// or a file by that name exists already, we just read that file
	FILE * fifo = fopen(filename, "r");
	if(!fifo){
		// fprintf(stderr, "fopen failed on fifo.\n");
		return -1;
	}
	// fifo's cannot be rewound!
#define BUFRMAX 12000
	char * buffr = new char[BUFRMAX];
	///int fsize = 0;
	int pos = 0;
	while( true ){
		char val = static_cast<char>(getc(fifo));
		if(val == EOF || val == '\n' || pos == BUFRMAX-1){
			buffr[pos] = 0;
			input_lines.push_back(string(buffr));
			if(val == EOF) break;
			pos = 0;
		}else{
			buffr[pos] = val;
			pos++;
		}
	};
	fclose(fifo);
	delete [] buffr;
	return rvalue;
#undef BUFRMAX 
};
/* ------------------------------------------------------------------------- */
	void terminal_readline
(   string & outstr, 
    const char * prompt, 
    const qstream & in, const qstream & out)
{
	FILE * store_rl_instream = rl_instream;
	FILE * store_rl_outstream = rl_outstream;
	// we have to cast away the constness because we need
	// to get the file handle associated with the stream
	// This is OK because the readline library will neither
	// close the stream nor reopen it.
	// We have to do this because readline is kind of an outdated
	// library.
	rl_instream = const_cast<qstream*>(&in)->get_handle();
	rl_outstream = const_cast<qstream*>(&out)->get_handle();
	char * linebufr =  readline(prompt);
	if( linebufr ){
		if( strlen(linebufr) > 0 ) add_history(linebufr);
	}

	rl_instream = store_rl_instream; // restore the readline default fstreams
	rl_outstream = store_rl_outstream; 
	if(linebufr){ // assign the results to the output string and free bufr
		if(strlen(linebufr) > 0){
			outstr = string(linebufr);
		}
		free(linebufr);
	}
};
/* ------------------------------------------------------------------------- */
// Returns true if the file is writeable as a true stream
// for things like writing messages, stdout etc ...
bool check_stream_output_status(const char * fname, const char * & errormsg){
	if( is_file_terminal_device(fname) ){
		return true;
	}
	if( is_file_regular(fname) ){
		errormsg = "Regular file already exists.";
		return false;
	}
	return true;
};
//#else
//#error POSIX is not defined on this system. File IO stream checking is not supported.
//#endif



//*********************************************************************************************//
void redirectoutputstreamsfornohup(){ // redirect streams if the process is now to ignore sighup
	// signals
	// for more user friendly handling of these errors, have a main.cc function check that this works
	// and return an error if it doesn't
	if(!freopen("nohup.stdout", "a", stdout)) fatal_stop("Redirection of stdout failed.");
	if(!freopen("nohup.stderr", "a", stderr)) fatal_stop("Redirection of stderr failed.");
	// standard in is a bit different
	FILE * new_stdin = fopen("nohup.stdin", "a");
	if(!new_stdin) fatal_stop("Redirection of stdin failed.");
	fclose(new_stdin);
	if(!freopen("nohup.stdin", "r", stdin)) fatal_stop("Redirection of stdin failed.");
};


//**************************************************************************//
//
// Class ncstream:
//
//***************************************************************************//
ncstream::ncstream() {
	this->ncbuf = NULL; 
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool ncstream::open(const char * fname, ncstream::filemode fmode){
	NcFile::FileMode mode;
	if(fmode == read) mode = NcFile::ReadOnly;
	else if(fmode == overwrite) mode = NcFile::Replace; 
	else if(fmode == newwrite) mode = NcFile::New; 
	else if(fmode == append) mode = NcFile::Write;
	else assert(false);
	this->ncbuf = new NcFile(fname, mode, NULL, 0, NcFile::Classic);
	if(!this->ncbuf->is_valid()){ 
		delete this->ncbuf;
		this->ncbuf = false;
		return false;
	}
	return true;
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool ncstream::write_variable_attributes( const char * varname, const vector< pair< string, string > > & attribs ){
	if(! this->ncbuf ) return false;
	NcVar * rvar = this->ncbuf->get_var(varname);
	if(rvar == NULL) return false;
	for(size_t u = 0; u < attribs.size(); u++){
		bool status = rvar->add_att( attribs[u].first.c_str(), attribs[u].second.c_str() );
		if(!status) return false;
	}
	return true; 
};
// *********************************************************************************//
//
// Class nchandler:
//
// *********************************************************************************//
nchandler::nchandler(){
	c_hid = 0;
};
// ................................................................................ //
nchandler::~nchandler(){
	// for(list<NcFile*>::iterator it = this->ncfiles.begin(); it != this->ncfiles.end(); it++){
	map<int, NcFile *>::iterator it;
	for(it = this->ncfiles.begin(); it != this->ncfiles.end(); it++){
		delete (*it).second;
	}
	this->ncfiles.clear();
};
// ................................................................................ //
int nchandler::createncfile(
		const char * path, 
		NcFile::FileMode mtype){ // returns handle
	NcFile * newncfile = new NcFile(path, mtype, NULL, 0, NcFile::Classic);
	if(newncfile->is_valid()){
		int hid = generate_hid();
		this->ncfiles.insert( pair< int, NcFile *>( hid, newncfile ) );
		return hid;
	}else{
		delete newncfile;
		return 0;
	}
};
// ................................................................................ //
int nchandler::generate_hid(){
	this->c_hid ++;
	return this->c_hid;
};
// ................................................................................ //
NcFile * nchandler::getncfile(const int & handle){
	if(handle <= 0) return NULL;
	map< int, NcFile * >::iterator it = this->ncfiles.find(handle);
	if( it != this->ncfiles.end() ){
		return (*it).second;
	}
	return NULL;
};
// ................................................................................ //
bool nchandler::deletencfile(const int & handle){
	if(handle <= 0) return NULL;
	map< int, NcFile * >::iterator it = this->ncfiles.find(handle);
	if( it != this->ncfiles.end() ){
		delete (*it).second;
		this->ncfiles.erase(it);
		return true;
	}
	return false;
} 

#endif
